

#include "lua_bitmap.h"

#define BMP_SIGNATURE "\x1B\x42\x6D\x70"
#define check_overflow(L, v, n)  if ((v) >= (n))  luaL_error(L, "overflow: %d", (v))
#define check_underflow(L, v, n) if ((v) <= (n))  luaL_error(L, "underflow: %d", (v))
#define check_powerof_2(v) if (!is_power_of_2(v)) luaL_error(L, "%d is not a power of 2", v)

/********************************************************************************/

struct class_mesh final {
  inline static const char* name() {
    return "bitmap";
  }
  inline static class_mesh* __this(lua_State* L) {
    return checkudata<class_mesh>(L, 1, name());
  }
  static int __gc(lua_State* L) {
    auto self = __this(L);
#ifdef LUA_DEBUG
    lua_ftrace("DEBUG: %s will gc\n", name());
#endif
    self->~class_mesh();
    return 0;
  }
  static bool check(const char* buff, size_t size) {
    const char* p1 = BMP_SIGNATURE;
    const char* p2 = buff;
    for (int i = 0; p1[i] && p2[i]; ++i) {
      if (p1[i] != p2[i]) {
        return false;
      }
    }
    return true;
  }
  static void update(class_mesh* self) {
    self->_bits  = self->_wbits * self->_hbits;
    self->_bytes = self->_bits >> 3;
    if (self->_bytes == 0) {
      if (self->_bits > 0) {
        self->_bytes = 1;
      }
    }
    auto n = self->_wbits;
    while (n >>= 1) self->_shift++;
  }
  static int load(lua_State* L) {
    clear(L);
    auto self = __this(L);
    const char* filename = luaL_checkstring(L, 2);
    FILE* fp = fopen(filename, "rb");
    if (!fp) {
      luaL_error(L, "%s open failed", filename);
      return 0;
    }
    size_t size = 0;
    if (fseek(fp, 0, SEEK_END) == 0) {
      size = ftell(fp);
      fseek(fp, 0, SEEK_SET);
    }
    /* BMP_SIGNATURE + width + height */
    if (size < 12) {
      fclose(fp);
      luaL_error(L, "%s size error", filename);
    }
    char buff[8192];
    fread(buff, 1, 4, fp);
    if (!check(buff, 4)) {
      fclose(fp);
      luaL_error(L, "%s format error", filename);
    }
    fread(buff, 1, 4, fp);
    ep_decode32(buff, (u32*)&self->_wbits);
    fread(buff, 1, 4, fp);
    ep_decode32(buff, (u32*)&self->_hbits);
    update(self);

    if (size - 12 != self->_bytes) {
      fclose(fp);
      luaL_error(L, "%s size error", filename);
    }
    self->_mesh.clear();
    while (!feof(fp)) {
      size_t n = fread(buff, 1, sizeof(buff), fp);
      self->_mesh.append(buff, n);
    }
    fclose(fp);
    return 0;
  }
  static int save(lua_State* L) {
    auto self = __this(L);
    const char* filename = luaL_checkstring(L, 2);
    FILE* fp = fopen(filename, "wb");
    if (!fp) {
      luaL_error(L, "%s open failed", filename);
      return 0;
    }
    char temp[16];
    auto n = fwrite(BMP_SIGNATURE, 1, 4, fp);
    ep_encode32(temp, (u32)self->_wbits);
    n += fwrite(temp, 1, 4, fp);
    ep_encode32(temp, (u32)self->_hbits);
    n += fwrite(temp, 1, 4, fp);
    n += fwrite(self->_mesh.c_str(), 1, self->_mesh.size(), fp);
    lua_pushinteger(L, n);
    fclose(fp);
    return 1;
  }
  static int clear(lua_State* L) {
    auto self = __this(L);
    self->_mesh.clear();
    self->_shift = 0;
    self->_bytes = self->_bits = 0;
    self->_wbits = self->_hbits = 0;
    return 0;
  }
  static int data(lua_State* L) {
    auto self = __this(L);
    if (self->_mesh.empty()) {
      lua_pushnil(L);
      return 1;
    }
    lua_pushlstring(L, self->_mesh.c_str(), self->_mesh.size());
    return 1;
  }
  static int size(lua_State* L) {
    auto self = __this(L);
    lua_pushinteger(L, self->_bytes);
    return 1;
  }
  static int bits(lua_State* L) {
    auto self = __this(L);
    lua_pushinteger(L, self->_bits);
    return 1;
  }
  static int width(lua_State* L) {
    auto self = __this(L);
    lua_pushinteger(L, self->_wbits);
    return 1;
  }
  static int height(lua_State* L) {
    auto self = __this(L);
    lua_pushinteger(L, self->_hbits);
    return 1;
  }
  static int index(lua_State* L) {
    auto self = __this(L);
    size_t x = luaL_checkunsigned(L, 2);
    check_overflow(L, x, self->_wbits);

    size_t y = luaL_checkunsigned(L, 3);
    check_overflow(L, y, self->_hbits);
    lua_pushinteger(L, y * self->_wbits + x + 1);
    return 1;
  }
  static int coord(lua_State* L) {
    auto self = __this(L);
    size_t n = self->_bits;
    auto i = luaL_checkunsigned(L, 2);
    check_underflow(L, i, 0);

    auto x = i - 1;
    check_overflow(L, (size_t)x, n);
    lua_pushinteger(L, x & (self->_wbits - 1));
    lua_pushinteger(L, x >> self->_shift);
    return 2;
  }
  static int convert(lua_State* L) {
    return lua_isnone(L, 3) ? coord(L) : index(L);
  }
  static int geti(lua_State* L) {
    auto self = __this(L);
    size_t n = self->_bytes;
    auto index = luaL_checkunsigned(L, 2);
    check_underflow(L, index, 0);

    auto i = index - 1;
    auto x = i >> 3; /* x = i / 8 */
    auto m = i  & 7; /* m = i % 8 */
    check_overflow(L, (size_t)x, n);

    auto mov = 7 - m;
    const char* p = self->_mesh.c_str();
    auto bit = (p[x] & (1 << mov)) >> mov;
    lua_pushinteger(L, bit);
    return 1;
  }
  static int seti(lua_State* L) {
    auto self = __this(L);
    size_t n = self->_bytes;
    auto index = luaL_checkunsigned(L, 2);
    check_underflow(L, index, 0);

    auto v = luaL_checkunsigned(L, 3) ? 1 : 0;
    auto i = index - 1;
    auto x = i >> 3; /* x = i / 8 */
    auto m = i  & 7; /* m = i % 8 */
    check_overflow(L, (size_t)x, n);

    auto mov = 7 - m;
    char* p = (char*)self->_mesh.c_str();
    if (v > 0) {
      p[x] |= (1 << mov);
    }
    else {
      p[x] &= (~(1 << mov));
    }
    return 1;
  }
  static int getxy(lua_State* L) {
    auto self = __this(L);
    size_t x = luaL_checkunsigned(L, 2);
    check_overflow(L, x, self->_wbits);
    size_t y = luaL_checkunsigned(L, 3);
    check_overflow(L, y, self->_hbits);

    lua_pop(L, 2);
    lua_pushinteger(L, y * self->_wbits + x + 1);
    return geti(L);
  }
  static int setxy(lua_State* L) {
    auto self = __this(L);
    size_t x = luaL_checkunsigned(L, 2);
    check_overflow(L, x, self->_wbits);
    size_t y = luaL_checkunsigned(L, 3);
    check_overflow(L, y, self->_hbits);

    lua_insert(L, 2);
    lua_pushinteger(L, y * self->_wbits + x + 1);
    lua_insert(L, 2);
    lua_pop(L, 2);
    return seti(L);
  }
  static int get(lua_State* L) {
    return lua_isnone(L, 3) ? geti(L) : getxy(L);
  }
  static int set(lua_State* L) {
    return lua_isnone(L, 4) ? seti(L) : setxy(L);
  }
  static int max_i(lua_State* L) {
    auto self = __this(L);
    lua_pushinteger(L, self->_wbits * self->_hbits);
    return 1;
  }
  static int max_x(lua_State* L) {
    auto self = __this(L);
    lua_pushinteger(L, self->_wbits ? self->_wbits - 1 : 0);
    return 1;
  }
  static int max_y(lua_State* L) {
    auto self = __this(L);
    lua_pushinteger(L, self->_hbits ? self->_hbits - 1 : 0);
    return 1;
  }
  static int min_i(lua_State* L) {
    auto self = __this(L);
    lua_pushinteger(L, 1);
    return 1;
  }
  static int min_x(lua_State* L) {
    auto self = __this(L);
    lua_pushinteger(L, 0);
    return 1;
  }
  static int min_y(lua_State* L) {
    auto self = __this(L);
    lua_pushinteger(L, 0);
    return 1;
  }
  static int resize(lua_State* L) {
    auto self = __this(L);
    self->_wbits = luaL_checkunsigned(L, 2);
    if (self->_wbits < 0) {
      self->_wbits = 0;
    }
    else if (self->_wbits > USHRT_MAX) {
      self->_wbits = USHRT_MAX + 1;
    }
    if (self->_wbits) {
      check_powerof_2(self->_wbits);
    }
    self->_hbits = luaL_checkunsigned(L, 3);
    if (self->_hbits < 0) {
      self->_hbits = 0;
    }
    else if (self->_hbits > USHRT_MAX) {
      self->_hbits = USHRT_MAX + 1;
    }
    if (self->_hbits) {
      check_powerof_2(self->_hbits);
    }
    update(self);
    size_t vbit = luaL_optunsigned(L, 4, 0);
    unsigned char c = (vbit == 0 ? 0 : 0xff);
    self->_mesh.clear();
    self->_mesh.resize(self->_bytes, (char)c);
    return 0;
  }
  static int create(lua_State* L) {
    auto self = newuserdata<class_mesh>(L, name());
    clear(L);
    return 1;
  }
  static void init_metatable(lua_State* L) {
    const luaL_Reg methods[] = {
      { "__gc",     __gc    },
      { "clear",    clear   },
      { "load",     load    },
      { "save",     save    },
      { "convert",  convert },
      { "max_i",    max_i   },
      { "max_x",    max_x   },
      { "max_y",    max_y   },
      { "min_i",    min_i   },
      { "min_x",    min_x   },
      { "min_y",    min_y   },
      { "get",      get     },
      { "set",      set     },
      { "resize",   resize  },
      { "data",     data    },
      { "size",     size    },
      { "bits",     bits    },
      { "width",    width   },
      { "height",   height  },
      { NULL,       NULL    }
    };
    newmetatable(L, name(), methods);
    lua_pop(L, 1);
  }
  static int open_library(lua_State* L) {
    init_metatable(L);
    const luaL_Reg methods[] = {
      { "bitmap",   create    },
      { NULL,       NULL      },
    };
    return new_module(L, "std", methods);
  }
  std::string _mesh;
  size_t _shift;
  size_t _wbits, _hbits, _bits, _bytes;
};

/********************************************************************************/

LUAOS_API int luaopen_bitmap(lua_State* L) {
  return class_mesh::open_library(L);
}

/********************************************************************************/
